當開發環境 (本機端) 要放上雲端成為正式環境時,會需要處理資料庫以及網頁伺服器 (Web Server) 兩大問題: 資料庫因為使用者不會再透過自己電腦取資料,需要於雲上建立一個新的雲端資料庫。網頁伺服器則是扮演幫助 Python 與接收前端伺服器 (能簡單理解為使用者),所轉發的動態請求的中間溝通角色:
最左邊 Client 指的是使用者瀏覽/使用機器人,最右邊 Django 則是我們所編寫的程式,可以發現中間還會經過兩道 Web Server,而我們需要處理位於 Django 與 Nginx 之間的 Web Server,圖示中 Nginx 的 Web Server 則會由 Heroku 協助代勞 (實務上 Heroku 並非使用 Nginx,但邏輯類似)。如果你對 WSGI 有興趣可以到 這裡,圖片來源也是來自此。
requirements.txt
以下步驟執行前記得啟動虛擬環境:
安裝雲資料庫與網頁伺服器套件
有別於本地端所使用的資料庫為 SQLite,Heroku 所使用的資料庫為 PostgreSQL,我們需要在requirements.txt
加上 psycopg2 ,其能協助 Python 操作連結 PostgreSQL;以及加上 gunicorn 網頁伺服器套件,Heroku 主要依靠它建置網站:
在虛擬環境中輸入:
$ pip install psycopg2
$ pip install gunicorn
安裝靜態檔案處理套件
比起動態的 Python 渲染網頁,靜態檔案 (HTML、CSS、JS) 存取頻率高,容易造成網頁伺服器負擔,所以會將其分開放置、壓縮與運行,在這裡可以安裝 WhiteNoise,協助我們處理靜態文件:
$ pip install whitenoise
安裝資料庫環境變數處理套件
在處理環境變數的資料庫配置時,可以透過 dj_database_url 工具將資料庫的 URL 字串(如 DATABASE_URL)轉換為 Django DATABASES 設置中需要的 Python 字典格式,讓 Django 可以使用,簡單來說是一個可以幫助我們處理資料庫環境變數的套件。
$ pip install dj_database_url
最後輸出 requirements.txt
在虛擬環境中與 hulolo
同層資料夾的指令輸入:
$ pip freeze > requirements.txt
這個動作可以將所有虛擬環境中的套件名稱與版本輸出,並且存成一在名為
requirements.txt
檔案中,Heroku 會識別此檔案進行套件安裝,建置環境在雲上。
需要編寫一個新檔案告訴 Heroku 應該如何執行網頁伺服器,我們在與 hulolo
同層資料夾的地方新建一個檔案名為Procfile
,內容輸入:
# hulolo > Procfile
web: gunicorn --pythonpath hulolo hulolo.wsgi --log-file -
如果專案名稱與教學文不同記得要更改哦。
調整部屬網域 ALLOWED_HOSTS
將其調整為自環境變數取值,ALLOWED_HOSTS 是一個安全參數,標示哪些網域名稱可以訪問 Django 應用程式。它的主要作用是防止 HTTP Host 標頭攻擊(Host Header Attack)。
# hulolo > hulolo > settings.py
ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", "").split(",")
因正式與開發環境的網址名稱不同,原先的
ALLOWED_HOSTS
可以將其註解或移除,改由環境變數取值,而環境變數套件dotenv
因無法讀取陣列 (array),這邊以split()
轉換為列表,相對的,在env
檔案中請添加ALLOWED_HOSTS = 127.0.0.1,a1fa-119-14-201-163.ngrok-free.app
。
調整資料夾路徑 DATABASE_URL
要讓程式可以辨識現在是本地端的資料庫還是雲端,可以加上判斷式進行處理,如果環境變數中有 DATABASE_URL
則認為在 Heroku 正式環境上。
# hulolo > hulolo > settings.py
import dj_database_url
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# 如果有 DATABASE_URL 這個環境變數,則使用它覆蓋預設的資料庫類型
DATABASE_URL = os.environ.get("DATABASE_URL")
if DATABASE_URL:
DATABASES["default"] = dj_database_url.config(default=DATABASE_URL, conn_max_age=600)
調整態檔案 MIDDLEWARE、STATIC_ROOT
在這邊我們要將 WhiteNoise 套件引入並放到中間層協助處理靜態檔案,並且建立一個存放這些檔案的資料夾 static,加上 STATICFILES_STORAGE 後讓 WhiteNoise 將壓縮後的靜態檔案進行處理。
# hulolo > hulolo > settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
# 加上 WhiteNoise Middleware
# 其他的 middleware...略
]
# 設定 static 靜態檔案的根目錄
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
# 啟用壓縮與緩衝存取的控制
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
HTTPS 處理
現今主流網站必須使用 HTTPS 通訊協定,在這邊我們也需要對正式環境做處理:
# hulolo > hulolo > settings.py
_secure_proxy_ssl_header = os.getenv("SECURE_PROXY_SSL_HEADER")
if _secure_proxy_ssl_header:
SECURE_PROXY_SSL_HEADER = tuple(_secure_proxy_ssl_header.split(","))
SESSION_COOKIE_SECURE = os.getenv("SESSION_COOKIE_SECURE")
CSRF_COOKIE_SECURE = os.getenv("CSRF_COOKIE_SECURE")
SECURE_SSL_REDIRECT = os.getenv("SECURE_SSL_REDIRECT")
由於 Heroku 雲端並不需要 .env
文件,透過後台直接設定環境參數,這裡我們要將 manage.py
調整為當偵測到環境變數 DJANGO_ENV
不是 production
(即正式環境時),才去執行 otenv.read_dotenv()
。
# hulolo > manage.py
if __name__ == '__main__':
# 檢查環境變數來區分開發和生產環境
if os.getenv('DJANGO_ENV') == 'production':
print("PRODUCTION")
else:
# 如果不是生產環境,讀取 .env 文件
dotenv.read_dotenv()
print("DEV")
main()
關於 Heroku 的環境變數
DJANGO_ENV
如何設定會在下一篇說明。
.env
加上 DATABASE_URL
與 SECURE_PROXY_SSL_HEADER
,甚至是SESSION_COOKIE_SECURE
、CSRF_COOKIE_SECURE
、SECURE_SSL_REDIRECT
也不需要設置,因為預設即為 False,而我們在本地所使用的網址為 HTTP 非 HTTPS。在這篇文章中,我們學會了:
settings.py
參數與環境變數